package network;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * <code>MainSocketServer</code>
 * 
 * @author wangjunneil@gmail.com
 */
public class MainSocketServer {

	private static final int START_PORT = 58080;
	private ServerSocket serverSocket = null;
	private boolean isRunning = true;

    // 使用自定义线程池
    // private NetworkThreadPool threadPool = null;
    private final int POOL_SIZE = 2;
    private ExecutorService executorService = null; // 使用JDK线程池
	
	public MainSocketServer() { }

    private void buildConnect() throws NetworkException {
        try {
            /*
                端口设置为0为匿名端口由系统自动生成端口号
                ServerSocket serverSocket = new ServerSocket(0);

                backlog参数用来设定连接请求队列的长度，它将覆盖操作系统限定队列的最大长度，管理连接请求是由操作系统完成的，操作
                系统会把这些连接请求存储在一个先进先出的队列中，以下几种情况设置了backlog但仍然会使用操作系统限定的长度：
                1、backlog参数值大于操作系统设置的限定值
                2、backlog参数值小于等于0
                3、没有设置backlog参数
                ServerSocket serverSocket = new ServerSocket(58080, 3);
                备注：如何查看和设置操作系统限定长度参看Evernote笔记《Linux下高并发socket最大连接数所受的各种限制》

                设定绑定IP地址的构造方法适用于多网卡的情况下使用
                ServerSocket serverSocket = new ServerSocket(58080, InetAddress.getByName("192.168.1.100"));

                默认构造方法适用于在绑定端口号之前先设置ServerSocket一些选项，因为一旦服务器与端口绑定就意味着一些选项不能再改变
                ServerSocket serverSocket = new ServerSocket();
                serverSocket.setReuseAddress(true); // SO_REUSEADDR必须在绑定端口号之前设置才有效
                serverSocket.bind(new InetAddress(58080));

                ServerSocket选项
                1、SO_TIMEOUT：等待客户连接的超时时间，serverSocket.setSoTimeout(6000);
                ServerSocket使用accept时等待客户连接的超时时间，以毫秒为单位，默认值是0表示永不超时
                2、SO_REUSEADDR：是否允许新的ServerSocket绑定到旧的ServerSocket端口上，serverSocket.setReuseAddress(true);
                当网络上仍然有数据向旧的ServerSocket上传输数据时，允许绑定旧的ServerSocket端口上，因为端口释放需要时间。
                3、SO_RCVBUF：设定接收数据的缓冲区大小，字节为单位，serverSocket.setReceiveBufferSize(131072); 128kb
                传输大的数据块如HTTP或者FTP则采用大的缓冲区，若类似于telnet或者网络游戏则应采用小的缓冲区以提升效率
                备注：ServerSocket选项最好都在建立端口号之前使用
             */
            serverSocket = new ServerSocket(START_PORT);
            System.out.println("Server is running and Listenning on port " + START_PORT);

//          threadPool = new NetworkThreadPool(Runtime.getRuntime().availableProcessors() * POOL_SIZE);
            executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * POOL_SIZE);

            shutdownThread.start(); // 控制关闭Server的线程启动
        } catch (BindException e) {
            /*
                出现BindException异常的原因
                1、端口已经被其他服务器进程占用
                2、系统用户非超级用户不能绑定1~1023之间的端口
             */
            throw new NetworkException(e);
        } catch (SocketTimeoutException e) {
            /*
                java.net.SocketTimeoutException: Accept timed out
                当设置了ServerSocket选项SO_TIMEOUT时，accept等待超过设定的超时时间时抛出，见上面“ServerSocket选项”
             */
            throw new NetworkException(e);
        } catch (IOException e) {
            throw new NetworkException(e);
        }
    }
	
	public void startServer() throws NetworkException {
        buildConnect();

		Socket socket = null;
        try {
            try {
                while (isRunning) {
                    socket = serverSocket.accept();
                    System.out.println("New connection accept " + socket);

//                  threadPool.execute(new SocketHandler(socket));    // 将客户端通信交给线程池
                    executorService.execute(new SocketHandler(socket));
                }
            } finally {
                System.out.println("Server is shudown");
                if (socket != null) socket.close();
                if (serverSocket != null && serverSocket.isBound() && !serverSocket.isClosed())
                    serverSocket.close();
            }
        } catch (SocketException e) {
            /*
                当服务器正在发送数据时客户端断开连接
                java.net.SocketException: Connection reset peer
             */
            throw new NetworkException(e);
        } catch (IOException e) {
            throw new NetworkException(e);
        }

	}

    class SocketHandler implements Runnable {
        private Socket socket = null;
        public SocketHandler(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            BufferedReader br = null;
            PrintWriter pw = null;
            String line;
            try {
                try {
                    br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    pw = new PrintWriter(socket.getOutputStream(), true);
                    while ((line = br.readLine()) != null) {
                        if ("bye".equals(line)) break;

                        System.out.println("Client: " + line);
                        pw.println("Echo: " + line);
                    }
                } finally {
                    if (br != null) br.close();
                    if (pw != null) pw.close();
                    if (socket != null) socket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private Thread shutdownThread = new Thread() {
        // 控制关闭Server的ServerSocket
        private static final int PORT_FOR_SHUTDOWN = 58081;
        private ServerSocket serverSocketForShutwosn = null;
        public void start() {
            this.setDaemon(true);
            super.start();
        }
        @Override
        public void run() {
            try {
                serverSocketForShutwosn = new ServerSocket(PORT_FOR_SHUTDOWN);
                Socket socketForShutdown;
                while (isRunning) {
                    socketForShutdown = serverSocketForShutwosn.accept();
                    BufferedReader br = new BufferedReader(new InputStreamReader(socketForShutdown.getInputStream()));
                    String command = br.readLine();
                    if ("shutdown".equals(command)) {
                        socketForShutdown.getOutputStream().write("Server is Shutdownning...\r\n".getBytes());
                        isRunning = false;

                        // 请求线程池关闭
                        executorService.shutdown();

                        // 等待关闭线程池，每次等待的超时时间为30秒
                        while (!executorService.isTerminated()) {
                            try {
                                executorService.awaitTermination(30, TimeUnit.SECONDS);
                            } catch (InterruptedException e) {

                            }
                        }

                        serverSocket.close();
                        socketForShutdown.close();
                        serverSocketForShutwosn.close();
                    }
                }
            } catch (Exception e) {

            }
        }
    };
	
	public static void main(String[] args) {
        try {
            new MainSocketServer().startServer();
        } catch (NetworkException e) {
            e.printStackTrace();
        }
    }

}
